package parser

import (
	"fmt"
	"strconv"
	"strings"

	"gitee.com/zhi/binexpr/ast"
	"gitee.com/zhi/binexpr/token"
)

func parseVariableExpr(ctx *token.Tokenizer) (ast.Expr, error) {
	ctx.SkipIf(token.IsSpace)

	name, _ := ctx.NextIf(token.IsWord)

	return &ast.VariableExpr{Name: string(name)}, nil
}

func parseObjPropExpr(ctx *token.Tokenizer) (ast.Expr, error) {
	ctx.SkipIf(token.IsSpace)

	var ret ast.Expr

	nameExpr, err := token.TryParse(ctx, parseBacketExpr, parseVariableExpr)
	if err != nil {
		return nil, err
	}
	ret = nameExpr

loop:
	for ctx.HasNext() {
		switch ctx.Peek() {
		case '.':
			ctx.Next()
			name, _ := ctx.NextIf(token.IsWord)
			ret = &ast.ObjPropExpr{Obj: ret, Prop: name}
		case '[':
			ctx.Next()
			expr, err := parseExpr(ctx)
			if err != nil {
				return nil, err
			}
			ret = &ast.ObjIndexExpr{Obj: ret, Index: expr}
			if !ctx.AssertNext("]") {
				return nil, fmt.Errorf("expected ] but not found")
			}
		default:
			break loop
		}
	}

	return ret, nil
}

func parseValueExpr(ctx *token.Tokenizer) (ast.Expr, error) {
	return token.TryParse(ctx, parseStringExpr, parseNumberExpr, parseBoolExpr)
}

func parseStringExpr(ctx *token.Tokenizer) (ast.Expr, error) {
	ctx.SkipIf(token.IsSpace)

	if !ctx.AssertNext(`"`) {
		return nil, fmt.Errorf("not a string")
	}

	var buf strings.Builder
	for {
		str, ok := ctx.NextIf(token.IsOppositeOf(token.IsCharOf('"')))
		if !ok {
			return nil, fmt.Errorf("unfinished string")
		}
		_, err := buf.WriteString(str)
		if err != nil {
			return nil, err
		}
		if len(str) == 0 || str[len(str)-1] != '\\' {
			break
		}
		if len(str) >= 2 && str[len(str)-2] == '\\' {
			break
		}
		ctx.Next()
		buf.WriteRune('"')
	}
	ctx.Next()

	return &ast.ValueExpr{Value: buf.String()}, nil
}

func parseFuncExpr(ctx *token.Tokenizer) (ast.Expr, error) {
	ctx.SkipIf(token.IsSpace)

	nameExpr, err := parseObjPropExpr(ctx)
	if err != nil {
		return nil, err
	}

	ctx.SkipIf(token.IsSpace)

	if !ctx.AssertNext("(") {
		return nameExpr, nil
	}

	ctx.SkipIf(token.IsSpace)

	if ctx.AssertNext(")") {
		return &ast.FuncExpr{Name: nameExpr}, nil
	}

	var params []ast.Expr
paramsLoop:
	for ctx.HasNext() {
		ctx.SkipIf(token.IsSpace)

		expr, err := parseExpr(ctx)
		if err != nil {
			return nil, err
		}
		params = append(params, expr)

		ctx.SkipIf(token.IsSpace)

		switch ctx.Peek() {
		case ')':
			ctx.Next()
			break paramsLoop
		case ',':
			ctx.Next()
		default:
			return nil, fmt.Errorf("invalid parameters")
		}
	}

	return &ast.FuncExpr{Name: nameExpr, Params: params}, nil
}

func parseNumberExpr(ctx *token.Tokenizer) (ast.Expr, error) {
	ctx.SkipIf(token.IsSpace)

	numberStr, _ := ctx.NextIf(token.IsNumber)

	number, err := strconv.ParseInt(string(numberStr), 10, 64)
	if err != nil {
		return nil, err
	}
	return &ast.ValueExpr{Value: number}, nil
}

var bools = map[string]bool{"true": true, "false": false}

func parseBoolExpr(ctx *token.Tokenizer) (ast.Expr, error) {
	ctx.SkipIf(token.IsSpace)

	for b, val := range bools {
		if bLen := len(b); ctx.PeekN(bLen) == b {
			ctx.NextN(bLen)
			return &ast.ValueExpr{Value: val}, nil
		}
	}

	return nil, fmt.Errorf("not a boolean")
}

func parseBacketExpr(ctx *token.Tokenizer) (ast.Expr, error) {
	ctx.SkipIf(token.IsSpace)

	if !ctx.AssertNext("(") {
		return nil, fmt.Errorf("not a bracket expression")
	}

	ctx.SkipIf(token.IsSpace)

	expr, err := parseExpr(ctx)
	if err != nil {
		return nil, err
	}

	ctx.SkipIf(token.IsSpace)

	if !ctx.AssertNext(")") {
		return nil, fmt.Errorf("not a bracket expression")
	}

	return &ast.BracketExpr{Expr: expr}, nil
}

func parseTerm(ctx *token.Tokenizer) (ast.Expr, error) {
	ctx.SkipIf(token.IsSpace)

	var op ast.UnaryOperator
	switch ctx.Peek() {
	case '-':
		op = ast.UnaryOpNegative
	case '!':
		op = ast.UnaryOpInverse
	default:
	}

	if op != 0 {
		ctx.Next()
	}

	expr, err := token.TryParse(ctx, parseValueExpr, parseFuncExpr)
	if err != nil {
		return nil, err
	}

	if op == 0 {
		return expr, nil
	}
	return &ast.UnaryExpr{Operator: op, Expr: expr}, nil
}

func parseMulOperator(ctx *token.Tokenizer) (ast.Expr, error) {
	ctx.SkipIf(token.IsSpace)

	left, err := parseTerm(ctx)
	if err != nil {
		return nil, err
	}

	ctx.SkipIf(token.IsSpace)

	if !ctx.HasNext() {
		return left, nil
	}

	var op ast.OperatorType
	switch ctx.Peek() {
	case '*':
		op = ast.OpTypeMul
	case '/':
		op = ast.OpTypeDiv
	case '%':
		op = ast.OpTypeMod
	default:
		return left, nil
	}
	ctx.Next()

	ctx.SkipIf(token.IsSpace)

	right, err := parseMulOperator(ctx)
	if err != nil {
		return nil, err
	}

	return &ast.OperatorExpr{Operator: op, Left: left, Right: right}, nil
}

func parseAddOperator(ctx *token.Tokenizer) (ast.Expr, error) {
	ctx.SkipIf(token.IsSpace)

	left, err := parseMulOperator(ctx)
	if err != nil {
		return nil, err
	}

	ctx.SkipIf(token.IsSpace)

	if !ctx.HasNext() {
		return left, nil
	}

	var op ast.OperatorType
	switch ctx.Peek() {
	case '+':
		op = ast.OpTypeAdd
	case '-':
		op = ast.OpTypeSub
	default:
		return left, nil
	}
	ctx.Next()

	ctx.SkipIf(token.IsSpace)

	right, err := parseAddOperator(ctx)
	if err != nil {
		return nil, err
	}

	return &ast.OperatorExpr{Operator: op, Left: left, Right: right}, nil
}

func parseEqOperator(ctx *token.Tokenizer) (ast.Expr, error) {
	ctx.SkipIf(token.IsSpace)

	left, err := parseAddOperator(ctx)
	if err != nil {
		return nil, err
	}

	ctx.SkipIf(token.IsSpace)

	if !ctx.HasNext() {
		return left, nil
	}

	if !ctx.AssertNext("==") {
		return left, nil
	}

	ctx.SkipIf(token.IsSpace)

	right, err := parseEqOperator(ctx)
	if err != nil {
		return nil, err
	}

	return &ast.OperatorExpr{Operator: ast.OpTypeEq, Left: left, Right: right}, nil
}

func parseExpr(ctx *token.Tokenizer) (ast.Expr, error) {
	return parseEqOperator(ctx)
}

func Parse(text string) (ast.Expr, error) {
	ctx := token.NewTokenizer(text)
	expr, err := parseExpr(ctx)
	if err != nil {
		return nil, err
	}
	ctx.SkipIf(token.IsSpace)
	if ctx.HasNext() {
		return nil, fmt.Errorf("encounter unparsable data: %s", text)
	}
	return expr, nil
}
